﻿using System;
using System.Collections.Generic;

using SharpDX;
using SharpDX.D3DCompiler;
using SharpDX.Diagnostics;
using SharpDX.Direct3D;
using SharpDX.Direct3D10;
using SharpDX.DXGI;

using Device = SharpDX.Direct3D10.Device;

namespace DX11Engine
{
    public struct PositionNormalTextured
    {
        public Vector3 Position;
        public Vector4 Color;
        public Vector3 Normal;
        public Vector2 Tex;
        public Vector3 Tangent;
        public Vector3 Binormal;
        public Vector2 LMTex;
    }
        
    public class JEntityFactory
    {
        private InputElement[] InputLayout = new InputElement[] {
                new InputElement("POSITION", 0, Format.R32G32B32_Float, 0, 0),
                new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 12, 0),
                new InputElement("NORMAL", 0, Format.R32G32B32_Float, 28, 0),
                new InputElement("TEXCOORD", 0, Format.R32G32_Float, 40, 0),
                new InputElement("TANGENT", 0, Format.R32G32B32_Float, 48, 0),
                new InputElement("BINORMAL", 0, Format.R32G32B32_Float, 60, 0),
                new InputElement("TEXCOORD", 1, Format.R32G32_Float, 72, 0),
            };

        private Device _device;
        public JEntityFactory(Device device)
        { _device = device; }

        public static void GenerateTriangleNormalTangentBinormalData(ref PositionNormalTextured[] vertexData)
        {
            Vector3 defVec = new Vector3(0, 0, 0);
            int i = 0;
            if (vertexData.Length == 3)
            {
                int v0 = i, v1 = i + 1, v2 = i + 2;

                // Calculate the two vectors for this face:
                Vector3 fvec = vertexData[v1].Position - vertexData[v0].Position;
                Vector3 svec = vertexData[v0].Position - vertexData[v2].Position;

                // Calculate the tu and tv texture space vectors:
                Vector2 tuvec = vertexData[v1].Tex - vertexData[v0].Tex;
                Vector2 tuvec2 = vertexData[v0].Tex - vertexData[v2].Tex;

                // Calculate the denominator of the tangent/binormal equation:
                float den = 1f / (tuvec.X * tuvec2.Y - tuvec2.X * tuvec.Y);

                // Calculate the cross products and multiply by the coefficient to get the tangent and binormal.
                Vector3 binormal = new Vector3(
                        (tuvec2.Y * fvec.X - tuvec.Y * svec.X) * den,
                        (tuvec2.Y * fvec.Y - tuvec.Y * svec.Y) * den,
                        (tuvec2.Y * fvec.Z - tuvec.Y * svec.Z) * den
                    );

                Vector3 tangent = new Vector3(
                        (tuvec.X * svec.X - tuvec2.X * fvec.X) * den,
                        (tuvec.X * svec.Y - tuvec2.X * fvec.Y) * den,
                        (tuvec.X * svec.Z - tuvec2.X * fvec.Z) * den
                    );

                // Calculate Normal:
                Vector3 normal = Vector3.Cross(fvec, svec);

                // Normalize products:
                tangent.Normalize();
                binormal.Normalize();
                normal.Normalize();

                vertexData[v0].Tangent = vertexData[v1].Tangent = vertexData[v2].Tangent = defVec + tangent;
                vertexData[v0].Binormal = vertexData[v1].Binormal = vertexData[v2].Binormal = defVec + binormal;
                vertexData[v0].Normal = vertexData[v1].Normal = vertexData[v2].Normal = defVec + normal;
            }
            else {throw new Exception("Polygons may only be triangles!");}
        }

        public J3DObject LoadGilesMesh(string FileName, float Size = 0.1f)
        {
            if (_device == null) { throw new Exception("Device must be specified before object creation!"); }
            J3DObject j = new J3DObject(_device);

            Dictionary<MeshMaterial, PositionNormalTextured[]> rMeshData = PlugIns.FrecleGiles.LoadMesh(FileName, Size);

            j.ModelData = new MeshData[rMeshData.Count];
            int indexCounter = 0;
            foreach (KeyValuePair<MeshMaterial, PositionNormalTextured[]> kvp in rMeshData)
            {
                j.ModelData[indexCounter] = new MeshData() { MeshMaterial = kvp.Key, VertexData = kvp.Value };
                indexCounter++;
            }

            j.LayoutInputElements = InputLayout;

            return j;
        }

        public J3DObject LoadMQO(string FileName, float Size = 0.01f)
        {
            if (_device == null) { throw new Exception("Device must be specified before object creation!"); }
            J3DObject j = new J3DObject(_device);

            Dictionary<MeshMaterial, PositionNormalTextured[]> rMeshData = PlugIns.MQOReader.LoadMesh(FileName, Size);

            j.ModelData = new MeshData[rMeshData.Count];
            int indexCounter = 0;
            foreach(KeyValuePair<MeshMaterial, PositionNormalTextured[]> kvp in rMeshData)
            {
                j.ModelData[indexCounter] = new MeshData() { MeshMaterial = kvp.Key, VertexData = kvp.Value };
                indexCounter++;
            }

            j.LayoutInputElements = InputLayout;

            return j;
        }

        public J3DObject CreateTriangle(float Size = 1)
        {
            if (_device == null) { throw new Exception("Device must be specified before object creation!"); }
            J3DObject j = new J3DObject(_device);

            j.ModelData = new MeshData[] {
                new MeshData() { VertexData = new[] {
                new PositionNormalTextured() { Position = new Vector3(0.0f, 0f, Size * 0.5f), Color = new Vector4(1.0f, 0.0f, 0.0f, 1f), Tex = new Vector2(0,0)},
                new PositionNormalTextured() { Position = new Vector3(Size * 0.5f, Size * -0.5f, Size * 0.5f), Color = new Vector4(0.0f, 1.0f, 0.0f, 1f), Tex = new Vector2(1,1)},
                new PositionNormalTextured() { Position = new Vector3(Size * -0.5f, Size * -0.5f, Size * 0.5f), Color = new Vector4(0.0f, 0.0f, 1.0f, 1f), Tex = new Vector2(1,0)}
            } } };

            PositionNormalTextured[] vdata = j.ModelData[0].VertexData;
            GenerateTriangleNormalTangentBinormalData(ref vdata);
            j.ModelData[0].VertexData = vdata;

            j.LayoutInputElements = InputLayout;

            return j;
        }

        public J3DObject CreateCube(float Size = 1)
        {
            if (_device == null) { throw new Exception("Device must be specified before object creation!"); }
            J3DObject j = new J3DObject(_device);
            j.ModelData = new MeshData[] {
                new MeshData() { VertexData = new[] {
                        new PositionNormalTextured() { Position = new Vector3(-1.0f, -1.0f, -1.0f), Color = new Vector4(1f, 1.0f, 1f, 1f), Tex = new Vector2(0,0) }, // Front
                        new PositionNormalTextured() { Position = new Vector3(-1.0f,  1.0f, -1.0f), Color = new Vector4(1f, 1.0f, 1f, 1f), Tex = new Vector2(0,1) },
                        new PositionNormalTextured() { Position = new Vector3( 1.0f,  1.0f, -1.0f), Color = new Vector4(1f, 1.0f, 1f, 1f), Tex = new Vector2(1,1) },
                        new PositionNormalTextured() { Position = new Vector3(-1.0f, -1.0f, -1.0f), Color = new Vector4(1f, 1.0f, 1f, 1f), Tex = new Vector2(0,0) },
                        new PositionNormalTextured() { Position = new Vector3( 1.0f,  1.0f, -1.0f), Color = new Vector4(1f, 1.0f, 1f, 1f), Tex = new Vector2(1,1) },
                        new PositionNormalTextured() { Position = new Vector3( 1.0f, -1.0f, -1.0f), Color = new Vector4(1f, 1.0f, 1f, 1f), Tex = new Vector2(1,0) },
                                                                                                                                       
                        new PositionNormalTextured() { Position = new Vector3(-1.0f, -1.0f,  1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(0,0)  }, // BACK
                        new PositionNormalTextured() { Position = new Vector3( 1.0f,  1.0f,  1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(1,1)  },
                        new PositionNormalTextured() { Position = new Vector3(-1.0f,  1.0f,  1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(1,0)  },
                        new PositionNormalTextured() { Position = new Vector3(-1.0f, -1.0f,  1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(0,0)  },
                        new PositionNormalTextured() { Position = new Vector3( 1.0f, -1.0f,  1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(0,1)  },
                        new PositionNormalTextured() { Position = new Vector3( 1.0f,  1.0f,  1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(1,1)  },
                                                                                                                                       
                        new PositionNormalTextured() { Position = new Vector3(-1.0f, 1.0f, -1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(0,0)  }, // Top
                        new PositionNormalTextured() { Position = new Vector3(-1.0f, 1.0f,  1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(0,1)  },
                        new PositionNormalTextured() { Position = new Vector3( 1.0f, 1.0f,  1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(1,1)  },
                        new PositionNormalTextured() { Position = new Vector3(-1.0f, 1.0f, -1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(0,0)  },
                        new PositionNormalTextured() { Position = new Vector3( 1.0f, 1.0f,  1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(1,1)  },
                        new PositionNormalTextured() { Position = new Vector3( 1.0f, 1.0f, -1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(1,0)  },
                                                                                                                                
                        new PositionNormalTextured() { Position = new Vector3(-1.0f,-1.0f, -1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(0,0)  }, // Right
                        new PositionNormalTextured() { Position = new Vector3( 1.0f,-1.0f,  1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(1,1)  },
                        new PositionNormalTextured() { Position = new Vector3(-1.0f,-1.0f,  1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(1,0)  },
                        new PositionNormalTextured() { Position = new Vector3(-1.0f,-1.0f, -1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(0,0)  },
                        new PositionNormalTextured() { Position = new Vector3( 1.0f,-1.0f, -1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(0,1)  },
                        new PositionNormalTextured() { Position = new Vector3( 1.0f,-1.0f,  1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(1,1)  },
                                                                                                                                       
                        new PositionNormalTextured() { Position = new Vector3(-1.0f, -1.0f, -1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(0,0)  }, // Left
                        new PositionNormalTextured() { Position = new Vector3(-1.0f, -1.0f,  1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(0,1)  },
                        new PositionNormalTextured() { Position = new Vector3(-1.0f,  1.0f,  1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(1,1)  },
                        new PositionNormalTextured() { Position = new Vector3(-1.0f, -1.0f, -1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(0,0)  },
                        new PositionNormalTextured() { Position = new Vector3(-1.0f,  1.0f,  1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(1,1)  },
                        new PositionNormalTextured() { Position = new Vector3(-1.0f,  1.0f, -1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(1,0)  },
                                                                                                                                       
                        new PositionNormalTextured() { Position = new Vector3( 1.0f, -1.0f, -1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(0,0)  }, // Bottom
                        new PositionNormalTextured() { Position = new Vector3( 1.0f,  1.0f,  1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(1,1)  },
                        new PositionNormalTextured() { Position = new Vector3( 1.0f, -1.0f,  1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(1,0)  },
                        new PositionNormalTextured() { Position = new Vector3( 1.0f, -1.0f, -1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(0,0)  },
                        new PositionNormalTextured() { Position = new Vector3( 1.0f,  1.0f, -1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(0,1)  },
                        new PositionNormalTextured() { Position = new Vector3( 1.0f,  1.0f,  1.0f), Color = new Vector4(1f, 1f, 1f, 1f), Tex = new Vector2(1,1)  },
                } } };

            //j.VertexData = JEntityFactory.GenerateNormalTangentBinormalData(j.VertexData);

            j.LayoutInputElements = InputLayout;

            return j;
        }
    }
}
